package worker

import (
	"context"
	"crontab/common"
	clientv3 "go.etcd.io/etcd/client/v3"
)

type JobLock struct {
	Kv clientv3.KV
	Lease clientv3.Lease
	JobName string
	CancelFunc context.CancelFunc
	LeaseId clientv3.LeaseID
	IsLock bool // 上锁是否成功
}

func InitJobLock(jobName string,kv clientv3.KV,lease clientv3.Lease) *JobLock {
	return &JobLock{
		Kv:    kv,
		Lease: lease,
		JobName:jobName,
	}
}

// 加锁
func (lock *JobLock) TryLock() (err error) {
	var (
		leaseResp *clientv3.LeaseGrantResponse
		leaseKeepRespChan <-chan *clientv3.LeaseKeepAliveResponse
		leaseKeepResp *clientv3.LeaseKeepAliveResponse
		ctx context.Context
		cancelFunc context.CancelFunc
		txn clientv3.Txn
		lockKey string
		txnResp *clientv3.TxnResponse
	)
	// 创建租约
	if leaseResp,err = lock.Lease.Grant(context.TODO(),5);err != nil {
		return
	}
	ctx,cancelFunc = context.WithCancel(context.TODO())
	// 租约续租
	leaseKeepRespChan,err = lock.Lease.KeepAlive(ctx,leaseResp.ID)
	defer func(err error) {
		// 报错了才进行取消
		if err != nil {
			// 取消续租
			cancelFunc()
			// 取消租约
			lock.Lease.Revoke(context.TODO(),leaseResp.ID)
		}
	}(err)
	if err != nil {
		return
	}
	// 监听是否续租变动
	go func() {
		loop:
		for {
			select {
			case leaseKeepResp = <-leaseKeepRespChan:
				if leaseKeepResp == nil {
					// 租约应答,没有收到数据 -> 租约已过期 跳出监听
					break loop
				}
			}
		}
	}()
	// 创建事务 -> tx事务抢锁
	txn = lock.Kv.Txn(context.TODO())
	lockKey = common.ETCD_JOB_LOCK_DIR + lock.JobName
	txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey),"=",0)).
		Then(clientv3.OpPut(lockKey,"",clientv3.WithLease(leaseResp.ID))).
		Else(clientv3.OpGet(lockKey))
	if txnResp,err = txn.Commit();err != nil {
		return
	}
	if !txnResp.Succeeded {
		// 加锁未成功
		return common.ERR_LOCKED
	}
	// 写入租约ID和自动续租取消方法供后续执行完毕取消
	lock.LeaseId = leaseResp.ID
	lock.CancelFunc = cancelFunc
	lock.IsLock = true
	return
}

func (lock *JobLock) UnLock()  {
	if lock.IsLock {
		// 释放自动续租
		lock.CancelFunc()
		// 释放租约
		lock.Lease.Revoke(context.TODO(),lock.LeaseId)
	}

}